package com.pingan.haofang.searchcloud.common.rpc;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;

import javax.annotation.PostConstruct;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.MessageSource;
import org.springframework.context.NoSuchMessageException;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.pingan.haofang.framework.common.utils.ThreadContext;
import com.pingan.haofang.searchcloud.common.dto.PageDTO;
import com.pingan.haofang.searchcloud.common.exception.SystemException;

/**
 * RPC工具类封装
 *
 * @author LUYI374
 * @date 2017年3月14日
 * @since 1.0.0
 */
public class RPC {

    static Logger LOG = LoggerFactory.getLogger(RPC.class);

    private static Gson GSON = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create();
    private static JsonParser PARSER = new JsonParser();

    private static MessageSource messageSource;

    @PostConstruct
    public static void init(@Qualifier MessageSource messageSource) {
        RPC.messageSource = messageSource;
    }

    /**
     * 执行请求
     *
     * @param template
     * @param config
     * @param method
     * @param type
     * @param exceptionHandler
     * @param httpHeaders      请求头
     * @param args
     * @return
     */
    public static List<?> execute(RestTemplate template, RPCConfigProperties config, String method, Type type,
                                  RPCExceptionHandler exceptionHandler, HttpHeaders httpHeaders, Object... args) {
        String methodName = StringUtils.isNotBlank(method) ? method : config.getMethod();
        RPCRequest request = new RPCRequest(getTraceId(), config.getJsonrpc(), methodName, args);
        String requestBody = GSON.toJson(request);
        LOG.info("rpc invoke, location:[{}], request:[{}]", config.getUrl(), requestBody);
        try {
            HttpEntity<String> httpEntity = new HttpEntity<>(requestBody, httpHeaders);
            ResponseEntity<String> httpResult = template.exchange(config.getUrl(), HttpMethod.POST, httpEntity
                    , new ParameterizedTypeReference<String>() {
                    });

            if (httpResult.getStatusCode() != HttpStatus.OK) {
                LOG.error("rpc invoke error, location:[{}], response code:[{}]", config.getUrl(),
                        httpResult.getStatusCode());
                throw new SystemException(config.getDefaultErrorMessage());
            }

            LOG.debug("rpc invoke finish, location:[{}], response:[{}]", config.getUrl(), httpResult.getBody());
            // 解析响应体
            RPCResponse<?> response = GSON.fromJson(httpResult.getBody(), new RPCResponseTypeDefinition<>(type));
            return response.getResult().getDatas();
        } catch (Exception e) {
            if (exceptionHandler != null) {
                exceptionHandler.handle(e);
                return null;
            } else {
                LOG.error("rpc invoke error, location:{}", config.getUrl(), e);
                throw new SystemException(config.getDefaultErrorMessage(), e);
            }
        }
    }

    /**
     * 执行请求
     *
     * @param template
     * @param config
     * @param method
     * @param type
     * @param args
     * @return
     */
    public static List<?> execute(RestTemplate template, RPCConfigProperties config, String method, Type type,
                                  RPCExceptionHandler exceptionHandler, Object... args) {
        return execute(template, config, method, type, exceptionHandler, null, args);
    }


    /**
     * 执行请求返回LIST
     *
     * @param template
     * @param config
     * @param clazz
     * @param args
     * @return
     */
    public static <T> List<T> executeForList(RestTemplate template, RPCConfigProperties config, Class<T> clazz,
                                             Object... args) {
        return executeForList(template, config, null, clazz, args);
    }

    /**
     * 执行请求返回LIST
     *
     * @param template
     * @param config
     * @param method
     * @param clazz
     * @param args
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <T> List<T> executeForList(RestTemplate template, RPCConfigProperties config, String method,
                                             Class<T> clazz, Object... args) {
        return (List<T>) execute(template, config, method, clazz, null, args);
    }

    /**
     * 执行请求返回分页结果集
     *
     * @param template
     * @param config
     * @param clazz
     * @param args
     * @return
     */
    public static <T> PageDTO<T> executeForPage(RestTemplate template, RPCConfigProperties config, Class<T> clazz,
                                                Object... args) {
        return executeForPage(template, config, null, null, clazz, args);
    }

    /**
     * 执行请求返回分页结果集
     *
     * @param template
     * @param config
     * @param clazz
     * @param httpHeaders
     * @param args
     * @param <T>
     * @return
     */
    public static <T> PageDTO<T> executeForPage(RestTemplate template, RPCConfigProperties config, Class<T> clazz,
                                                HttpHeaders httpHeaders, Object... args) {
        return executeForPage(template, config, null, null, clazz, httpHeaders, args);
    }


    /**
     * 执行请求返回分页结果集
     *
     * @param template
     * @param config
     * @param method
     * @param clazz
     * @param args
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <T> PageDTO<T> executeForPage(RestTemplate template, RPCConfigProperties config, String method,
                                                RPCExceptionHandler exceptionHandler, Class<T> clazz, Object... args) {
        return executeForPage(template, config, method, exceptionHandler, clazz, null, args);
    }

    /**
     * 执行请求返回分页结果集
     *
     * @param template
     * @param config
     * @param method
     * @param clazz
     * @param args
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <T> PageDTO<T> executeForPage(RestTemplate template, RPCConfigProperties config, String method,
                                                RPCExceptionHandler exceptionHandler, Class<T> clazz,
                                                HttpHeaders httpHeaders, Object... args) {
        List<RPCPage<T>> result = (List<RPCPage<T>>) execute(template, config, method,
                new PageTypeDefinition<>(clazz), exceptionHandler, httpHeaders, args);

        if (CollectionUtils.isEmpty(result)) {
            return null;
        }

        RPCPage<T> page = result.get(0);
        int totalPages = 1;
        if (page.getPageSize() > 0) {
            totalPages = (int) (page.getTotal() % page.getPageSize() == 0 ? page.getTotal() / page.getPageSize()
                    : page.getTotal() / page.getPageSize() + 1);
        }

        return PageDTO.builder(page.getDatas()).pageNo(page.getPageNo()).pageSize(page.getPageSize())
                .totalRecords(page.getTotal()).totalPages(totalPages).build();
    }


    /**
     * RPC执行
     *
     * @param requestBody
     * @return
     */
    public static RPCResponse<Object> rpcInvoke(String requestBody) {

        RPCResult<Object> result = new RPCResult<Object>();
        String parseErrorMessage = "rpc invoke fail, request parse error";

        String traceId = null;
        JsonArray paramsVals = null;
        String methodName = null;

        // 请求体解析
        try {
            JsonObject request = (JsonObject) PARSER.parse(requestBody);

            if (request == null) {
                LOG.error("{}, request:{}", parseErrorMessage, requestBody);
                result.setErrorCode(RPCErrorCode.FAIL.value);
                result.setErrorMessage(parseErrorMessage);
                return new RPCResponse<>("", result);
            }

            traceId = request.get("id").getAsString();
            methodName = request.get("method").getAsString();
            if (StringUtils.isEmpty(methodName)) {
                LOG.error("{}, request:{}", parseErrorMessage, requestBody);
                result.setErrorCode(RPCErrorCode.FAIL.value);
                result.setErrorMessage(parseErrorMessage);
                return new RPCResponse<>(traceId, result);
            }

            paramsVals = (JsonArray) request.get("params");
            if (paramsVals == null) {
                LOG.error("{}, request:{}", parseErrorMessage, requestBody);
                result.setErrorCode(RPCErrorCode.FAIL.value);
                result.setErrorMessage(parseErrorMessage);
                return new RPCResponse<>(traceId, result);
            }

        } catch (Exception e) {
            LOG.error("{}, request:{}", parseErrorMessage, requestBody, e);
            result.setErrorCode(RPCErrorCode.FAIL.value);
            result.setErrorMessage(parseErrorMessage);
            return new RPCResponse<>(traceId, result);
        }

        // 获取RPC执行器
        RPCInvoker invoker = RPCFactory.getInvoker(methodName);

        if (invoker == null) {
            String invokerErrorMessage = "rpc invoke fail, get rpc invoker fail";
            LOG.error("{}, request:{}", invokerErrorMessage, requestBody);
            result.setErrorCode(RPCErrorCode.FAIL.value);
            result.setErrorMessage(invokerErrorMessage);
            return new RPCResponse<>(traceId, result);
        }

        // 校验参数是否合法
        Type[] paramTypes = invoker.getMethod().getGenericParameterTypes();
        if (paramsVals.size() != paramTypes.length) {
            String invokerErrorMessage = "rpc invoke fail, param illegal";
            LOG.error("{}, request:{}", invokerErrorMessage, requestBody);
            result.setErrorCode(RPCErrorCode.FAIL.value);
            result.setErrorMessage(invokerErrorMessage);
            return new RPCResponse<>(traceId, result);
        }

        // 参数注入
        Object[] params = new Object[paramTypes.length];
        try {
            int i = 0;
            for (Iterator<JsonElement> iter = paramsVals.iterator(); iter.hasNext(); ) {
                JsonElement ele = iter.next();
                params[i] = GSON.fromJson(ele, paramTypes[i]);
                i++;
            }
        } catch (Exception e) {
            String invokerErrorMessage = "rpc invoke fail, param illegal";
            LOG.error("{}, request:{}", invokerErrorMessage, requestBody, e);
            result.setErrorCode(RPCErrorCode.FAIL.value);
            result.setErrorMessage(invokerErrorMessage);
            return new RPCResponse<>(traceId, result);
        }

        // 执行
        Object returnVal = null;
        try {
            returnVal = invoker.getMethod().invoke(invoker.getBean(), params);
            result.setErrorCode(RPCErrorCode.SUCCESS.value);
            if (!"void".equalsIgnoreCase(invoker.getMethod().getReturnType().getName())) {
                result.setDatas(Arrays.asList(returnVal));
            } else {
                result.setDatas(new ArrayList<>(0));
            }
        } catch (SystemException e) {
            result.setErrorCode(RPCErrorCode.FAIL.value);
            if (e.getMessage() != null) {
                String message = e.getMessage();
                try {
                    message = messageSource.getMessage(e.getMessage(), e.getArgs(), LocaleContextHolder.getLocale());
                } catch (NoSuchMessageException ex) {
                    ; // ignore
                }
                result.setErrorMessage(message);
            }
            return new RPCResponse<>(traceId, result);
        } catch (Exception e) {
            String invokerErrorMessage = "rpc invoke fail, invoke error";
            LOG.error("{}, request:{}", invokerErrorMessage, requestBody, e);
            result.setErrorCode(RPCErrorCode.FAIL.value);
            result.setErrorMessage(invokerErrorMessage);
            return new RPCResponse<>(traceId, result);
        }

        RPCResponse<Object> response = new RPCResponse<Object>(traceId, result);
        return response;
    }

    /**
     * 生成跟踪ID
     *
     * @return
     */
    private static String getTraceId() {
        String traceId = ThreadContext.getRequestId();
        if (StringUtils.isNotBlank(traceId)) {
            return traceId;
        }
        return UUID.randomUUID().toString();
    }

    /**
     * RPC响应类型定义
     *
     * @param <T>
     * @author LUYI374
     * @date 2017年3月14日
     * @since 1.0.0
     */
    public static class RPCResponseTypeDefinition<T> implements ParameterizedType {

        private Type clazz;

        public RPCResponseTypeDefinition(Type clazz) {
            this.clazz = clazz;
        }

        @Override
        public Type[] getActualTypeArguments() {
            return new Type[]{clazz};
        }

        @Override
        public Type getRawType() {
            return RPCResponse.class;
        }

        @Override
        public Type getOwnerType() {
            return null;
        }
    }

    /**
     * page类型定义
     *
     * @param <T>
     * @author LUYI374
     * @date 2017年3月14日
     * @since 1.0.0
     */
    public static class PageTypeDefinition<T> implements ParameterizedType {

        private Class<T> clazz;

        public PageTypeDefinition(Class<T> clazz) {
            this.clazz = clazz;
        }

        @Override
        public Type[] getActualTypeArguments() {
            return new Type[]{clazz};
        }

        @Override
        public Type getRawType() {
            return RPCPage.class;
        }

        @Override
        public Type getOwnerType() {
            return null;
        }
    }

    /**
     * list类型定义
     *
     * @param <T>
     * @author LUYI374
     * @date 2017年3月14日
     * @since 1.0.0
     */
    public static class ListTypeDefinition<T> implements ParameterizedType {

        private Class<T> clazz;

        public ListTypeDefinition(Class<T> clazz) {
            this.clazz = clazz;
        }

        @Override
        public Type[] getActualTypeArguments() {
            return new Type[]{clazz};
        }

        @Override
        public Type getRawType() {
            return List.class;
        }

        @Override
        public Type getOwnerType() {
            return null;
        }
    }

}